import 'dart:convert';

import 'package:my_flutter_core/config/general_config.dart';
import 'package:my_flutter_core/extensions/string_extension.dart';
import 'package:my_flutter_core/models/user.dart';
import 'package:my_flutter_core/services/local_storage.dart';
import 'package:my_flutter_core/services/token_service.dart';

/// The service provides method against authentication.
///
/// Current active user info is saved in local storage with key [GeneralConfig.localUserInfoStorageKey].
/// This is used to determine if user is logged in or not.
///
/// The active user access token is saved in local storage with key [GeneralConfig.localAccessTokenStorageKey].
///
/// Once the quick login is enabled all logged in user infos are saved in local storage with key [GeneralConfig.localUserInfoCollectionStorageKey].
/// All the infos will be shown in a [ListView] for quick login selection.
/// User can tap on any one in the list and verfiy the fringerprint to login quickly.
///
/// The quick login user access tokens are saved in local storage with key of usernames. Why the tokens are saved separatly due to the its info length.
/// Access token and refresh token contain thousands of strings. Saving all of them into one list might not be a good idea for local storage.
///
/// Does not change the original user info and access token key names because we can easily add/remove the quick login feature without affect other ones.
///
/// IMPORTANT!!!
/// Never ever saves user plain password on client side!!!
class AuthStateService {
  AuthStateService._();

  static final AuthStateService instance = AuthStateService._();

  final enableQuickLoginDefaultValue = false;

  // The cached user data.
  User? _currentUser;

  // The cached user list data for quick login.
  Map<String?, User?>? _userCollection = {};

  /// Gets current saved user info.
  Future<User?> get currentUser async {
    // Because reading data from local is slow. We need to cache the data for next usage.
    if (_currentUser == null) {
      // Reads the user info from local storage as string format.
      final userInfo = await LocalStorage.get<String>(
        GeneralConfig.localUserInfoStorageKey,
      );

      if (userInfo.isNotNullOrEmpty) {
        try {
          // Decodes the string to json object.
          final userInfoDecoded = json.decode(userInfo!);

          // Returns the user instance.
          _currentUser = User.fromJson(userInfoDecoded);
        } catch (_) {
          _currentUser = null;
        }
      }
    }

    return _currentUser;
  }

  /// Saved current username for next logic form auto fill input.
  Future<String?> get currentUsername async =>
      LocalStorage.get<String>(GeneralConfig.localCurrentUsernameStorageKey);

  /// If current user is authenticated or not.
  Future<bool> get isAuthenticated async =>
      await currentUser != null && await TokenService.hasRefreshToken;

  Future<Map<String?, User?>?> get userCollection async {
    // Because reading data from local is slow. We need to cache the data for next usage.
    if (_userCollection == null) {
      // Reads the user info list from local storage as string format.
      final userCollection = await LocalStorage.get<String>(
        GeneralConfig.localUserInfoCollectionStorageKey,
      );

      if (userCollection.isNotNullOrEmpty) {
        try {
          // Decodes the string to json object.
          final decodedCollection =
              json.decode(userCollection!) as Map<String, dynamic>;

          // Converts the [Map<String, dynamic>] to [Map<String, User>].
          _userCollection = decodedCollection.map(
            (key, value) => MapEntry(key, User.fromJson(value)),
          );
        } catch (_) {
          _userCollection = {};
        }
      }
    }

    return _userCollection;
  }

  /// If has any quick login user info.
  Future<bool> get hasQuickLoginCollection async =>
      (await userCollection)!.isNotEmpty;

  /// Saves current [user] on local.
  void saveUserInfo(User user) {
    // Caches the updated user data.
    _currentUser = user;

    // Converts the [user] to json format.
    final userInfo = user.toJson();

    // Encodes the json data to json format string.
    final encodedData = json.encode(userInfo);

    // Saves the encoded json format string on local.
    LocalStorage.set<String>(
      GeneralConfig.localUserInfoStorageKey,
      encodedData,
    );
  }

  /// Saves current username. This is for logic form with saved username.
  Future<void> saveCurrentUsername({String? username}) async {
    username ??= _currentUser?.username;

    if (username.isNotNullOrEmpty) {
      await LocalStorage.set<String?>(
        GeneralConfig.localCurrentUsernameStorageKey,
        username,
      );
    }
  }

  /// Removes current username. This is for logic form with saved username.
  Future<void> removeCurrentUsername() async {
    await LocalStorage.remove(GeneralConfig.localCurrentUsernameStorageKey);
  }

  /// Clears local user info.
  Future<void> removeUserInfo() async {
    _currentUser = null;
    await LocalStorage.remove(GeneralConfig.localUserInfoStorageKey);
  }

  /// Checks if the [user] is in the [userCollection].
  Future<bool> isUserInCollection(User? user) async {
    if (user == null) {
      return false;
    }

    return (await userCollection)!.containsKey(getMapKey(user));
  }

  /// Saves [user] to [userCollection] to enable quick login feature.
  Future<void> saveUserToCollection(User user) async {
    // Adds the user to the collection.
    final collection = await userCollection;
    collection![getMapKey(user)] = user;

    await _saveUserCollection((await userCollection)!);
  }

  /// Removes [user] from [userCollection] to disable quick login feature.
  Future<void> removeFromUserInfoCollection(User user) async {
    // Removes the user from the collection if it exists.
    final collection = await userCollection;
    collection!.remove(getMapKey(user));

    await _saveUserCollection(collection);
  }

  /// Gets the key for the map to identify each user.
  ///
  /// It is used for saving user info collection and access token in local storage.
  String? getMapKey(User? user) => user?.id;

  Future<void> _saveUserCollection(Map<String?, User?> userCollection) async {
    // Encodes the json data to json format string.
    final encodedData = json.encode(userCollection);

    // Saves the encoded json format string on local.
    await LocalStorage.set<String>(
      GeneralConfig.localUserInfoCollectionStorageKey,
      encodedData,
    );
  }
}
